<!DOCTYPE html>
<html class="has-navbar-fixed-top">
<head>
    <meta charset="utf-8">
<title>SpringBoot开发 - wanzixin</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.css">


<link href="/Study/Java/SpringBoot/zh-cn/SpringBoot%E5%BC%80%E5%8F%91/" rel="alternate" hreflang="en" />
    


<meta name="description" content="">





    <meta name="description" content="我们在前面详细介绍了Spring框架，它的主要功能包括IoC容器、AOP支持、事务支持、MVC开发以及强大的第三方集成功能等。那么SpringBoot又是什么？它和Spring是什么关系？">
<meta property="og:type" content="article">
<meta property="og:title" content="SpringBoot开发">
<meta property="og:url" content="https://wanzixin.github.io/Study/Java/SpringBoot/zh-cn/SpringBoot%E5%BC%80%E5%8F%91/index.html">
<meta property="og:site_name" content="wanzixin">
<meta property="og:description" content="我们在前面详细介绍了Spring框架，它的主要功能包括IoC容器、AOP支持、事务支持、MVC开发以及强大的第三方集成功能等。那么SpringBoot又是什么？它和Spring是什么关系？">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-07-14T02:20:48.000Z">
<meta property="article:modified_time" content="2021-07-24T08:49:55.819Z">
<meta property="article:author" content="wanzixin">
<meta name="twitter:card" content="summary">





<link rel="icon" href="/favicon.png">


<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Ovo|Source+Code+Pro">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/css/lightgallery.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/css/justifiedGallery.min.css">


<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css">


<link rel="stylesheet" href="/css/style.css">


<script defer src="//use.fontawesome.com/releases/v5.0.8/js/all.js"></script>


    
    
    
    
    
    
    
    
    
    

    


<meta name="generator" content="Hexo 5.4.0"></head>
<body>
    
<nav class="navbar is-transparent is-fixed-top navbar-main" role="navigation" aria-label="main navigation">
    <div class="container">
        <div class="navbar-brand">
            <a class="navbar-item navbar-logo" href="/zh-cn">
                
                    
                    wanzixin
                    
                
            </a>
            <div class="navbar-burger">
                <span></span>
                <span></span>
                <span></span>
            </div>
        </div>
        
        <div class="navbar-menu navbar-start">
            
            <a class="navbar-item "
               href="/archives">Archives</a>
            
            <a class="navbar-item "
               href="/categories">Categories</a>
            
            <a class="navbar-item "
               href="/categories/Diary">Diary</a>
            
            <a class="navbar-item "
               href="/categories/Gallery">Gallery</a>
            
            <a class="navbar-item "
               href="/categories/Study">Study</a>
            
            <a class="navbar-item "
               href="/categories/Item">Item</a>
            
            <a class="navbar-item "
               href="/about">About</a>
            
        </div>
        
        <div class="navbar-menu navbar-end">
            
            <a class="navbar-item search" title="搜索" href="javascript:;">
                <i class="fas fa-search"></i>
            </a>
            
            
            <div class="navbar-item is-hoverable has-dropdown is-hidden-mobile is-hidden-tablet-only toc">
                <a class="navbar-item" title="目录">
                    <i class="fa fa-list"></i>
                </a>
                <div class="navbar-dropdown is-right">
                    
                    
                    
                    
                    <a class="navbar-item" href="#第一个SpringBoot应用">1&nbsp;&nbsp;<b>第一个SpringBoot应用</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#application-yml">1.1&nbsp;&nbsp;application.yml</a>
                    
                    
                    
                    <a class="navbar-item" href="#使用环境变量">1.2&nbsp;&nbsp;使用环境变量</a>
                    
                    
                    
                    <a class="navbar-item" href="#logback-spring-xml">1.3&nbsp;&nbsp;logback-spring.xml</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用开发者工具">2&nbsp;&nbsp;<b>使用开发者工具</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#打包SpringBoot应用">3&nbsp;&nbsp;<b>打包SpringBoot应用</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#瘦身SpringBoot应用">4&nbsp;&nbsp;<b>瘦身SpringBoot应用</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#使用spring-boot-thin-launcher">4.1&nbsp;&nbsp;使用spring-boot-thin-launcher</a>
                    
                    
                    
                    <a class="navbar-item" href="#预热">4.2&nbsp;&nbsp;预热</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Actuator">5&nbsp;&nbsp;<b>使用Actuator</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Profiles">6&nbsp;&nbsp;<b>使用Profiles</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#使用Conditional">7&nbsp;&nbsp;<b>使用Conditional</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#加载配置文件">8&nbsp;&nbsp;<b>加载配置文件</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#禁用自动配置">9&nbsp;&nbsp;<b>禁用自动配置</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#小结">9.1&nbsp;&nbsp;小结</a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#添加Filter">10&nbsp;&nbsp;<b>添加Filter</b></a>
                    
                    
                    <hr class="navbar-divider">
                    
                    
                    <a class="navbar-item" href="#集成第三方组件">11&nbsp;&nbsp;<b>集成第三方组件</b></a>
                    
                    
                    
                    <a class="navbar-item" href="#集成Open-API">11.1&nbsp;&nbsp;集成Open API</a>
                    
                    
                    
                    <a class="navbar-item" href="#配置反向代理">11.1.1&nbsp;&nbsp;配置反向代理</a>
                    
                    
                    
                    <a class="navbar-item" href="#访问Redis">11.2&nbsp;&nbsp;访问Redis</a>
                    
                    
                    
                    <a class="navbar-item" href="#集成Artemis">11.3&nbsp;&nbsp;集成Artemis</a>
                    
                </div>
            </div>
            
            
            <a class="navbar-item" title="GitHub" target="_blank" rel="noopener" href="https://github.com/wanzixin">
                
                <i class="fab fa-github"></i>
                
            </a>
               
            
        </div>
    </div>
</nav>

    <section class="section">
    <div class="container">
    <article class="article content gallery" itemscope itemprop="blogPost">
    <h1 class="article-title is-size-3 is-size-4-mobile" itemprop="name">
        
            SpringBoot开发
        
    </h1>
    <div class="article-meta columns is-variable is-1 is-multiline is-mobile is-size-7-mobile">
        <span class="column is-narrow">
            
                <span>7月 14 2021</span>
            
        </span>
        
        <span class="column is-narrow article-category">
            <i class="far fa-folder"></i>
            <a class="article-category-link" href="/zh-cn/categories/Study/">Study</a><span>></span><a class="article-category-link" href="/zh-cn/categories/Study/Java/">Java</a><span>></span><a class="article-category-link" href="/zh-cn/categories/Study/Java/SpringBoot/">SpringBoot</a>
        </span>
        
        
        <span class="column is-narrow">
            
            
            1 小时 读完 (约 9834 字)
        </span>
        
    </div>
    <div class="article-entry is-size-6-mobile" itemprop="articleBody">
    
        <html><head></head><body><p>我们在前面详细介绍了Spring框架，它的主要功能包括IoC容器、AOP支持、事务支持、MVC开发以及强大的第三方集成功能等。那么SpringBoot又是什么？它和Spring是什么关系？<span id="more"></span>SpringBoot是一个基于Spring的套件，它帮我们预组装了Spring的一系列组件，以便以尽可能少的代码和配置来开发基于Spring的Java应用程序，它们不是取代关系，试图跳过Spring直接学习SpringBoot是不可能的。SpringBoot的目标就是提供一个开箱即用的应用程序架构，我们基于SpringBoot的预置结构继续开发是省时省力的。本章我们详细介绍如何使用SpringBoot。</p>
<h2 id="第一个SpringBoot应用"><a href="#第一个SpringBoot应用" class="headerlink" title="第一个SpringBoot应用"></a>第一个SpringBoot应用</h2><p>我们新建一个<code>springboot-hello</code>的工程，创建标准的Maven目录结构如下：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ascii">springboot-hello<br>├── pom.xml<br>├── src<br>│   └── main<br>│       ├── java<br>│       └── resources<br>│           ├── application.yml<br>│           ├── logback-spring.xml<br>│           ├── static<br>│           └── templates<br>└── target<br></code></pre></td></tr></tbody></table></figure>

<p>其中，在<code>src/main/resources</code>目录下，注意到几个文件：</p>
<h3 id="application-yml"><a href="#application-yml" class="headerlink" title="application.yml"></a>application.yml</h3><p>这是SpringBoot默认的配置文件，它采用YAML格式而不是<code>.properties</code>格式，因为YAML格式更易读，文件名必须是<code>application.yml</code>而不是其他名称。</p>
<blockquote>
<p>YAML是一种层级格式，它和<code>.properties</code>很容易互相转换，它的优点是去掉了大量重复的前缀，更加易读。SpringBoot中也可以使用<code>application.properties</code>作为配置文件。</p>
</blockquote>
<h3 id="使用环境变量"><a href="#使用环境变量" class="headerlink" title="使用环境变量"></a>使用环境变量</h3><p>在配置文件中，我们经常使用如下格式对某个key进行配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">app:</span><br>  <span class="hljs-attr">db:</span><br>    <span class="hljs-attr">host:</span> <span class="hljs-string">${DB_HOST:localhost}</span><br>    <span class="hljs-attr">user:</span> <span class="hljs-string">${DB_USER:root}</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">${DB_PASSWORD:password}</span><br></code></pre></td></tr></tbody></table></figure>

<p>这种<code>${DB_HOST:localhost}</code>意思是，首先从环境变量查找<code>DB_HOST</code>，如果环境变量定义了，那么使用环境变量的值，否则，使用默认值<code>localhost</code>。类似地，这使得我们在开发和部署时更加方便，因为开发时无需设置任何环境变量，直接使用默认值，而实际线上运行时，只需要传入环境变量即可：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">$ DB_HOST=10.0.1.123 DB_USER=prod DB_PASSWORD=xxxx java -jar xxx.jar<br></code></pre></td></tr></tbody></table></figure>

<h3 id="logback-spring-xml"><a href="#logback-spring-xml" class="headerlink" title="logback-spring.xml"></a>logback-spring.xml</h3><p>这是SpringBoot的logback配置文件名称（也可以使用<code>logback.xml</code>），一个标准的写法如下：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">include</span> <span class="hljs-attr">resource</span>=<span class="hljs-string">"org/springframework/boot/logging/logback/defaults.xml"</span> /&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">appender</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"CONSOLE"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.ConsoleAppender"</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">encoder</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">pattern</span>&gt;</span>${CONSOLE_LOG_PATTERN}<span class="hljs-tag">&lt;/<span class="hljs-name">pattern</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">charset</span>&gt;</span>utf8<span class="hljs-tag">&lt;/<span class="hljs-name">charset</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">encoder</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">appender</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">appender</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"APP_LOG"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.rolling.RollingFileAppender"</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">encoder</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">pattern</span>&gt;</span>${FILE_LOG_PATTERN}<span class="hljs-tag">&lt;/<span class="hljs-name">pattern</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">charset</span>&gt;</span>utf8<span class="hljs-tag">&lt;/<span class="hljs-name">charset</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">encoder</span>&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">file</span>&gt;</span>app.log<span class="hljs-tag">&lt;/<span class="hljs-name">file</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">rollingPolicy</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.rolling.FixedWindowRollingPolicy"</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">maxIndex</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">maxIndex</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">fileNamePattern</span>&gt;</span>app.log.%i<span class="hljs-tag">&lt;/<span class="hljs-name">fileNamePattern</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">rollingPolicy</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">triggeringPolicy</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">MaxFileSize</span>&gt;</span>1MB<span class="hljs-tag">&lt;/<span class="hljs-name">MaxFileSize</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">triggeringPolicy</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">appender</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">root</span> <span class="hljs-attr">level</span>=<span class="hljs-string">"INFO"</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">appender-ref</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"CONSOLE"</span> /&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">appender-ref</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"APP_LOG"</span> /&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">root</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>它主要通过<code>&lt;include resource="..."&gt;</code>引入SpringBoot的缺省配置，这样我们就可以引用类似<code>${CONSOLE_LOG_PATTERN}</code>这样的变量。上述配置定义了一个控制台输出和文件输出，可根据需要修改。</p>
<p><code>static</code>是静态文件目录，<code>templates</code>是模板文件目录。注意它们不再存放在<code>src/main/webapp</code>下，而是直接放到<code>src/main/resources</code>这个classpath目录下，因为SpringBoot已经不需要专门的<code>webapp</code>目录了。</p>
<p>以上就是SpringBoot的标准目录结构，它完全是一个基于Java应用的普通Java项目。</p>
<p>我们再来看源码目录结构：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs ascii">src/main/java<br>└── com<br>    └── itranswarp<br>        └── learnjava<br>            ├── Application.java<br>            ├── entity<br>            │   └── User.java<br>            ├── service<br>            │   └── UserService.java<br>            └── web<br>                └── UserController.java<br></code></pre></td></tr></tbody></table></figure>

<p>在存放源码的<code>src/main/java</code>目录中，SpringBoot对Java包的层级结构有一个要求。注意到我们的根package是<code>com.itranswarp.learnjava</code>，下面还有<code>entity</code>、<code>service</code>、<code>web</code>等子package。SpringBoot要求<code>main()</code>方法所在的启动类必须放到根package下，命名不做要求，这里我们以<code>Application.java</code>命名，它的内容如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{<br>        SpringApplication.run(Application.class, args);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>启动SpringBoot应用程序只需要一行代码加上一个注解<code>@SpringBootApplication</code>，该注解实际上又包含了：</p>
<ul>
<li>SpringBootConfiguration<ul>
<li>Configuration</li>
</ul>
</li>
<li>EnableAutoConfiguration<ul>
<li>AutoConfigurationPackage</li>
</ul>
</li>
<li>ComponentScan</li>
</ul>
<p>这样一个注解就相当于启动了自动配置和自动扫描。</p>
<p>我们再观察<code>pom.xml</code>，它的内容如下：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">...</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.3.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.itranswarp.learnjava<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>springboot-hello<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.source</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.source</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.target</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.target</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">pebble.version</span>&gt;</span>3.1.2<span class="hljs-tag">&lt;/<span class="hljs-name">pebble.version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-jdbc<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>        <span class="hljs-comment">&lt;!-- 集成Pebble View --&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.pebbletemplates<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>pebble-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${pebble.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><br>        <span class="hljs-comment">&lt;!-- JDBC驱动 --&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.hsqldb<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>hsqldb<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>使用SpringBoot时，强烈推荐从<code>spring-boot-starter-parent</code>继承，因为这样就可以引入SpringBoot的预置配置。紧接着，我们引入了依赖<code>spring-boot-starter-web</code>和<code>spring-boot-starter-jdbc</code>，它们分别引入了Spring MVC和Spring JDBC的相关依赖，无需指定版本号，因为引入的<code>&lt;parent&gt;</code>内已经指定了，只有我们自己引入的某些第三方jar包需要指定版本号。</p>
<p>这里我们引入<code>pebble-spring-boot-starter</code>作为View，以及<code>hsqldb</code>作为嵌入式数据库。<code>hsqldb</code>已在<code>spring-boot-starter-jdbc</code>中预置了版本号<code>2.5.0</code>，因此此处无需指定版本号。</p>
<p>根据<code>pebble-spring-boot-starter</code>的<a target="_blank" rel="noopener" href="https://pebbletemplates.io/wiki/guide/spring-boot-integration/">文档</a>，加入如下配置到<code>application.yml</code>：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">pebble:</span><br>  <span class="hljs-comment"># 默认为".pebble"，改为"":</span><br>  <span class="hljs-attr">suffix:</span><br>  <span class="hljs-comment"># 开发阶段禁用模板缓存:</span><br>  <span class="hljs-attr">cache:</span> <span class="hljs-literal">false</span><br></code></pre></td></tr></tbody></table></figure>

<p>对<code>Application</code>稍作改动，添加<code>WebMvcConfigurer</code>这个Bean：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> </span>{<br>    ...<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">WebMvcConfigurer <span class="hljs-title">createWebMvcConfigurer</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> HandlerInterceptor[] interceptors)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> WebMvcConfigurer() {<br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addResourceHandlers</span><span class="hljs-params">(ResourceHandlerRegistry registry)</span> </span>{<br>                <span class="hljs-comment">// 映射路径`/static/`到classpath路径:</span><br>                registry.addResourceHandler(<span class="hljs-string">"/static/**"</span>)<br>                        .addResourceLocations(<span class="hljs-string">"classpath:/static/"</span>);<br>            }<br>        };<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>现在就可以直接运行<code>Application</code>，启动后观察Spring Boot的日志。SpringBoot自动启动了嵌入式Tomcat，当看到<code>Started Application in xxx seconds</code>时，Spring Boot应用启动成功。现在，我们在浏览器输入<code>localhost:8080</code>就可以直接访问页面。</p>
<p>那么前面我们定义的数据源、声明式事务、JdbcTemplate在哪创建的？怎么就可以直接注入到自己编写的<code>UserService</code>中呢？这些自动创建的Bean就是SpringBoot的特色：AutoConfiguration。当我们引入<code>spring-boot-starter-web</code>后，启动时会自动扫描所有的<code>XxxAutoConfiguration</code>：</p>
<ul>
<li>DataSourceAutoConfiguration：自从创建一个DataSource，其中配置项从<code>application.yml</code>的<code>spring.datasource</code>读取</li>
<li>DataSourceTransactionManagerAutoConfiguration：自动创建一个基于JDBC的事务管理器</li>
<li>JdbcTemplateAutoConfiguration：自动创建了一个JdbcTemplate</li>
</ul>
<p>因此，我们自动得到了一个<code>DataSource</code>、一个<code>DataSourceTransactionManager</code>和一个<code>JdbcTemplate</code>。</p>
<p>类似地，当我们引入<code>spring-boot-starter-web</code>时，自动创建了：</p>
<ul>
<li>ServletWebServerFactoryAutoConfiguration：自动创建一个嵌入式Web服务器，默认是Tomcat；</li>
<li>DispatcherServletAutoConfiguration：自动创建一个<code>DispatcherServlet</code>；</li>
<li>HttpEncodingAutoConfiguration：自动创建一个<code>CharacterEncodingFilter</code>；</li>
<li>WebMvcAutoConfiguration：自动创建若干与MVC相关的Bean。</li>
<li>…</li>
</ul>
<p>引入第三方pepple-spring-boot-starter时，自动创建了：</p>
<ul>
<li>PebbleAutoConfiguration：自动创建了一个<code>PebbleViewResolver</code></li>
</ul>
<p>SpringBoot大量的使用<code>XxxAutoConfiguration</code>来使得许多组件被自动化配置并创建，而这些创建过程又大量的使用了Spring的Conditional的功能。</p>
<p>例如，我们观察<code>JdbcTemplateAutoConfiguration</code>，它的代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration(proxyBeanMethods = false)</span><br><span class="hljs-meta">@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })</span><br><span class="hljs-meta">@ConditionalOnSingleCandidate(DataSource.class)</span><br><span class="hljs-meta">@AutoConfigureAfter(DataSourceAutoConfiguration.class)</span><br><span class="hljs-meta">@EnableConfigurationProperties(JdbcProperties.class)</span><br><span class="hljs-meta">@Import({ JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JdbcTemplateAutoConfiguration</span> </span>{<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>当满足条件：</p>
<ul>
<li><code>@ConditionalOnClass</code>：在classpath中能找到<code>DataSource</code>和<code>JdbcTemplate</code></li>
<li><code>@ConditionalOnSingleCandidate(DataSource.class)</code>：在当前Bean的定义中能找到唯一的<code>DataSource</code></li>
</ul>
<p>该<code>JdbcTemplateAutoConfiguration</code>就会起作用，实际创建由导入的<code>JdbcTemplateConfiguration</code>完成：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration(proxyBeanMethods = false)</span><br><span class="hljs-meta">@ConditionalOnMissingBean(JdbcOperations.class)</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JdbcTemplateConfiguration</span> </span>{<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-meta">@Primary</span><br>    <span class="hljs-function">JdbcTemplate <span class="hljs-title">jdbcTemplate</span><span class="hljs-params">(DataSource dataSource, JdbcProperties properties)</span> </span>{<br>        JdbcTemplate jdbcTemplate = <span class="hljs-keyword">new</span> JdbcTemplate(dataSource);<br>        JdbcProperties.Template template = properties.getTemplate();<br>        jdbcTemplate.setFetchSize(template.getFetchSize());<br>        jdbcTemplate.setMaxRows(template.getMaxRows());<br>        <span class="hljs-keyword">if</span> (template.getQueryTimeout() != <span class="hljs-keyword">null</span>) {<br>            jdbcTemplate.setQueryTimeout((<span class="hljs-keyword">int</span>) template.getQueryTimeout().getSeconds());<br>        }<br>        <span class="hljs-keyword">return</span> jdbcTemplate;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>创建<code>JdbcTemplate</code>之前，要满足<code>@ConditionalOnMissingBean(JdbcOperations.class)</code>，即不存在<code>JdbcOperations</code>的Bean。</p>
<p>如果我们自己创建了一个JdbcTemplate：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> </span>{<br>    ...<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">JdbcTemplate <span class="hljs-title">createJdbcTemplate</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JdbcTemplate(dataSource);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>那么根据条件<code>@ConditionalOnMissingBean(JdbcOperations.class)</code>，SpringBoot就不会再创建一个重复的<code>JdbcTemplate</code>（因为<code>JdbcOperations</code>是<code>JdbcTemplate</code>的父类）。</p>
<p>可见，Spring Boot自动装配功能是通过<strong>自动扫描+条件装配</strong>实现的，这一套机制在默认情况下工作得很好，但是，如果我们要手动控制某个Bean的创建，就需要详细地了解Spring Boot自动创建的原理，很多时候还要跟踪<code>XxxAutoConfiguration</code>，以便设定条件使得某个Bean不会被自动创建。</p>
<h2 id="使用开发者工具"><a href="#使用开发者工具" class="headerlink" title="使用开发者工具"></a>使用开发者工具</h2><p>在开发阶段，我们经常需要修改代码，然后重启SpringBoot应用。经常手动停止再启动，比较麻烦。SpingBoot提供了一个开发者工具，可以<strong>监控classpath路径上的文件</strong>。只要源码或配置文件发生修改，SpringBoot应用就可以自动重启。在开发阶段，这个功能比较有用。</p>
<p>只需要添加如下依赖：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-devtools<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>直接启动应用程序，然后试着修改源码、保存，观察日志输出，SpringBoot会自动重新加载。默认配置下，针对<code>/static</code>、<code>/public</code>和<code>/templates</code>目录中的文件修改，不会自动重启，因为禁用缓存后，这些文件的修改可以实时更新。</p>
<h2 id="打包SpringBoot应用"><a href="#打包SpringBoot应用" class="headerlink" title="打包SpringBoot应用"></a>打包SpringBoot应用</h2><p>我们在Maven的使用插件一节中介绍了如何使用<code>maven-shade-plugin</code>打包一个可执行的jar包。在SpringBoot应用中，打包更简单，因为SpringBoot自带一个更简单的<code>spring-boot-maven-plugin</code>插件来打包，我们只需要在<code>pom.xml</code>中加入以下配置：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">...</span>&gt;</span><br>    ...<br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>无需任何配置，这个插件就可以自动定位应用程序的入口Class，执行以下命令就可以打包：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">$ mvn clean package<br></code></pre></td></tr></tbody></table></figure>

<p>以<code>springboot-exec-jar</code>项目为例，打包后在<code>target</code>目录可以看到以下两个<code>jar</code>文件：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">$ ls<br>classes<br>generated-sources<br>maven-archiver<br>maven-status<br>springboot-exec-jar-1.0-SNAPSHOT.jar<br>springboot-exec-jar-1.0-SNAPSHOT.jar.original<br></code></pre></td></tr></tbody></table></figure>

<p>其中，<code>springboot-exec-jar-1.0-SNAPSHOT.jar.original</code>是Maven标准打包插件打的jar包，它只包含我们自己的Class，不包含依赖，而<code>springboot-exec-jar-1.0-SNAPSHOT.jar</code>是Spring Boot打包插件创建的包含依赖的jar，可以直接运行：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">$ java -jar springboot-exec-jar-1.0-SNAPSHOT.jar<br></code></pre></td></tr></tbody></table></figure>

<p>这样，部署一个Spring Boot应用就非常简单，无需预装任何服务器，只需要上传jar包即可。</p>
<p>在打包的时候，因为打包后的Spring Boot应用不会被修改，因此，默认情况下，<code>spring-boot-devtools</code>这个依赖不会被打包进去。但是要注意，使用早期的Spring Boot版本时，需要配置一下才能排除<code>spring-boot-devtools</code>这个依赖：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">excludeDevtools</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">excludeDevtools</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>如果不喜欢默认的项目名+版本号作为文件名，可以加一个配置指定文件名：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">...</span>&gt;</span><br>    ...<br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">finalName</span>&gt;</span>awesome-app<span class="hljs-tag">&lt;/<span class="hljs-name">finalName</span>&gt;</span><br>        ...<br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>这样打包后的文件名就是<code>awesome-app.jar</code>。</p>
<h2 id="瘦身SpringBoot应用"><a href="#瘦身SpringBoot应用" class="headerlink" title="瘦身SpringBoot应用"></a>瘦身SpringBoot应用</h2><p>上一节中，我们使用SpringBoot提供的<code>spring-boot-maven-plugin</code>打包SpringBoot应用，可以直接获得一个完整的可运行的jar包，把它上传到服务器上再运行就极其方便。但是，这种方式最大的缺点是包太大了，动不动几十MB，在网速不给力的情况下，上传服务器非常耗时。并且，其中我们引入的Tomcat、Spring和其他第三方组件，只要版本号不变，这些jar就相当于每次都重复打进去，再重复上传了一遍。</p>
<p>真正经常改动的是我们自己编写的代码，如果只打包我们自己的代码，通常jar包也就几百KB。但是，运行的时候，classpath里没有依赖的包，肯定会报错。</p>
<p>那如何只打包我们自己编写的代码，同时又自动把依赖包下载到某处，并自动引入到classpath中？答案就是使用<code>spring-boot-thin-launcher</code>。</p>
<h3 id="使用spring-boot-thin-launcher"><a href="#使用spring-boot-thin-launcher" class="headerlink" title="使用spring-boot-thin-launcher"></a>使用spring-boot-thin-launcher</h3><p>我们先演示如何使用，再详细介绍工作原理。</p>
<p>首先复制一份上一节的Maven项目，并重命名为<code>spring-thin-jar</code>：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">...</span>&gt;</span><br>    ...<br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.itranswarp.learnjava<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>springboot-thin-jar<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    ...<br></code></pre></td></tr></tbody></table></figure>

<p>然后，修改<code>&lt;build&gt;-&lt;plugins&gt;-&lt;plugin&gt;</code>，给原来的<code>spring-boot-maven-plugin</code>增加一个<code>dependency</code>如下：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">...</span>&gt;</span><br>    ...<br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">finalName</span>&gt;</span>awesome-app<span class="hljs-tag">&lt;/<span class="hljs-name">finalName</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot.experimental<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-thin-layout<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0.27.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>到此为止不再需要其他改动。接下来按照正常流程打包，执行<code>mvn clean package</code>，观察<code>target</code>目录最终生成的可执行<code>awesome-app.jar</code>，只占很小的体积。</p>
<p>直接运行<code>java -jar awesome-app.jar</code>，效果和上节完全一样。</p>
<p>实际上，<code>spring-boot-thin-launcher</code>这个插件改变了<code>spring-boot-maven-plugin</code>的默认行为。它输出的jar包只包含我们自己代码编译后的class、一个很小的<code>ThinJarWrapper</code>，以及解析<code>pom.xml</code>后得到的所有依赖jar的列表。</p>
<p>运行的时候，入口实际上是<code>ThinJarWrapper</code>，它会先在指定目录搜索看看依赖的jar包是否都存在，如果不存在，先从Maven中央仓库下载到本地，然后，再执行我们自己编写的<code>main()</code>入口方法。这种方式有点类似于在线安装程序：用户下载后得到的是一个很小的exe安装程序，执行安装程序时，会首先在先下载所需的若干巨大的文件，再进行安装。</p>
<p>这个<code>spring-boot-thin-launcher</code>在启动时搜索的默认目录是用户主目录的<code>.m2</code>，我们也可以指定下载目录，例如，将下载目录指定为当前目录：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">$ java -Dthin.root=. -jar awesome-app.jar<br></code></pre></td></tr></tbody></table></figure>

<p>上述命令通过环境变量<code>thin.root</code>传入当前目录，执行后发现当前目录下自动生成了一个<code>repository</code>目录，这和Maven的默认下载目录<code>~/.m2/repository</code>的结构是完全一样的，只是它仅包含<code>awesome-app.jar</code>所需的运行期依赖项。</p>
<blockquote>
<p>注意：只有首次运行时会自动下载依赖项，再次运行时由于无需下载，所以启动速度会大大加快。如果删除了repository目录，再次运行时就会再次触发下载。</p>
</blockquote>
<h3 id="预热"><a href="#预热" class="headerlink" title="预热"></a>预热</h3><p>把79KB大小的<code>awesome-app.jar</code>直接扔到服务器执行，上传过程就非常快。但是，第一次在服务器上运行<code>awesome-app.jar</code>时，仍需要从Maven中央仓库下载大量的jar包，所以，<code>spring-boot-thin-launcher</code>还提供了一个<code>dryrun</code>选项，专门用来下载依赖项而不执行实际代码：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">java -Dthin.dryrun=true -Dthin.root=. -jar awesome-app.jar<br></code></pre></td></tr></tbody></table></figure>

<p>执行上述代码会在当前目录创建<code>repository</code>目录，并下载所有依赖项，但并不会运行我们编写的<code>main()</code>方法。此过程称之为“预热”（warm up）。</p>
<p>如果服务器由于安全限制不允许从外网下载文件，那么可以在本地预热，然后把<code>awesome-app.jar</code>和<code>repository</code>目录上传到服务器。只要依赖项没有变化，后续改动只需要上传<code>awesome-app.jar</code>即可。</p>
<h2 id="使用Actuator"><a href="#使用Actuator" class="headerlink" title="使用Actuator"></a>使用Actuator</h2><p>在生产环境中，需要对应用程序的状态进行监控。前面我们介绍了使用JMX对程序包括JVM进行监控，我们回忆一下：使用JMX需要把一些监控信息以MBean的形式暴露给JMX Server。而SpringBoot已经内置了一个监控功能，它叫Actuator。</p>
<p>使用Actuator非常简单，只需要添加如下依赖：</p>
<figure class="highlight xml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></tbody></table></figure>

<p>然后启动应用程序，Actuator就会把它能收集到的所有信息暴露给JMX。此外，Actuator还可以通过URL<code>/actuator/</code>挂载一些监控点，例如，输入<code>http://localhost:8080/actuator/health</code>，我们可以查看应用程序当前状态：</p>
<figure class="highlight json hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs json">{<br>    <span class="hljs-attr">"status"</span>: <span class="hljs-string">"UP"</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>许国网关作为反向代理需要一个URL来探测后端集群应用是否存活，这个URL就可以提供给网关使用。</p>
<p>Actuator默认把所有访问点暴露给JMX，但出于安全原因，只有<code>health</code>和<code>info</code>会暴露给Web。Actuator提供的所有访问点均在官方文档列出，要暴露更多的访问点给Web，需要在<code>application.yml</code>中加上配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">management:</span><br>  <span class="hljs-attr">endpoints:</span><br>    <span class="hljs-attr">web:</span><br>      <span class="hljs-attr">exposure:</span><br>        <span class="hljs-attr">include:</span> <span class="hljs-string">info,</span> <span class="hljs-string">health,</span> <span class="hljs-string">beans,</span> <span class="hljs-string">env,</span> <span class="hljs-string">metrics</span><br></code></pre></td></tr></tbody></table></figure>

<p>要特别注意暴露URL的安全性，例如，<code>/actuator/env</code>可以获取当前机器的所有环境变量，不可暴露给公网。</p>
<h2 id="使用Profiles"><a href="#使用Profiles" class="headerlink" title="使用Profiles"></a>使用Profiles</h2><p>Profile本身是Spring提供的功能，我们在使用<a href="https://wanzixin.github.io/Study/Java/Spring/IoC%E5%AE%B9%E5%99%A8/#%E4%BD%BF%E7%94%A8%E6%9D%A1%E4%BB%B6%E8%A3%85%E9%85%8D">条件装配</a>中讲到了，Profile表示一个环境，如开发、测试和生产这三个环境：</p>
<ul>
<li>native</li>
<li>test</li>
<li>production</li>
</ul>
<p>或者按git分支定义的master、dev这些环境：</p>
<ul>
<li>master</li>
<li>dev</li>
</ul>
<p>在启动一个应用程序时，可以传入一个或多个环境，例如：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">-Dspring.profiles.active=test,master<br></code></pre></td></tr></tbody></table></figure>

<p>大多数情况下，一个环境就足够了。</p>
<p>SpringBoot对Profiles的支持在于，可以在<code>application.yml</code>中为每个环境进行配置，下面是一个示例配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">${APP_NAME:unnamed}</span><br>  <span class="hljs-attr">datasource:</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:hsqldb:file:testdb</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">sa</span><br>    <span class="hljs-attr">password:</span><br>    <span class="hljs-attr">dirver-class-name:</span> <span class="hljs-string">org.hsqldb.jdbc.JDBCDriver</span><br>    <span class="hljs-attr">hikari:</span><br>      <span class="hljs-attr">auto-commit:</span> <span class="hljs-literal">false</span><br>      <span class="hljs-attr">connection-timeout:</span> <span class="hljs-number">3000</span><br>      <span class="hljs-attr">validation-timeout:</span> <span class="hljs-number">3000</span><br>      <span class="hljs-attr">max-lifetime:</span> <span class="hljs-number">60000</span><br>      <span class="hljs-attr">maximum-pool-size:</span> <span class="hljs-number">20</span><br>      <span class="hljs-attr">minimum-idle:</span> <span class="hljs-number">1</span><br><br><span class="hljs-attr">pebble:</span><br>  <span class="hljs-attr">suffix:</span><br>  <span class="hljs-attr">cache:</span> <span class="hljs-literal">false</span><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-string">${APP_PORT:8080}</span><br><br><span class="hljs-meta">---</span><br><span class="hljs-meta"></span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">profiles:</span> <span class="hljs-string">test</span><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">8000</span><br><br><span class="hljs-meta">---</span><br><span class="hljs-meta"></span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">profiles:</span> <span class="hljs-string">production</span><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">80</span><br><br><span class="hljs-attr">pebble:</span><br>  <span class="hljs-attr">cache:</span> <span class="hljs-literal">true</span><br></code></pre></td></tr></tbody></table></figure>

<p>注意到分隔符<code>---</code>，最前面的配置是默认配置，不需要指定Profile，后面的每段配置都必须以<code>spring.profiles: xxx</code>开头，表示一个Profile。上述配置默认使用8080端口，但在test环境下使用8000端口，在production环境下，使用80端口，并启用Pepple的缓存。</p>
<p>如果我们不指定任何Profile，直接启动应用程序，那么Profile实际上就是default，可以从SpringBoot启动日志看到。</p>
<p>要以test环境启动，可输入以下命令：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">$ java -Dspring.profiles.active=test -jar springboot-profiles-1.0-SNAPSHOT.jar<br></code></pre></td></tr></tbody></table></figure>

<p>可以在日志中看到活动的Profile是test，Tomcat的监听端口是8000。</p>
<p>通过Profile可以实现一套代码在不同环境启用不同的配置和功能。</p>
<p>我们来举个例子。</p>
<p>假设我们需要一个存储服务，在本地开发时，直接使用文件存储即可，但是，在测试和生产环境，需要存储到云端如S3上，如何通过Profile实现该功能？</p>
<p>首先，我们要定义存储接口<code>StorageService</code>：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">public interface StorageService {<br><br>    // 根据URI打开InputStream:<br>    InputStream openInputStream(String uri) throws IOException;<br><br>    // 根据扩展名+InputStream保存并返回URI:<br>    String store(String extName, InputStream input) throws IOException;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>本地存储可通过<code>LocalStorageService</code>实现：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Profile("default")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocalStorageService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageService</span> </span>{<br>    <span class="hljs-meta">@Value("${storage.local:/var/static}")</span><br>    String localStorageRootDir;<br><br>    <span class="hljs-keyword">final</span> Logger logger = LoggerFactory.getLogger(getClass());<br><br>    <span class="hljs-keyword">private</span> File localStorageRoot;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        logger.info(<span class="hljs-string">"Intializing local storage with root dir: {}"</span>, <span class="hljs-keyword">this</span>.localStorageRootDir);<br>        <span class="hljs-keyword">this</span>.localStorageRoot = <span class="hljs-keyword">new</span> File(<span class="hljs-keyword">this</span>.localStorageRootDir);<br>    }<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> InputStream <span class="hljs-title">openInputStream</span><span class="hljs-params">(String uri)</span> <span class="hljs-keyword">throws</span> IOException </span>{<br>        File targetFile = <span class="hljs-keyword">new</span> File(<span class="hljs-keyword">this</span>.localStorageRoot, uri);<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BufferedInputStream(<span class="hljs-keyword">new</span> FileInputStream(targetFile));<br>    }<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">store</span><span class="hljs-params">(String extName, InputStream input)</span> <span class="hljs-keyword">throws</span> IOException </span>{<br>        String fileName = UUID.randomUUID().toString() + <span class="hljs-string">"."</span> + extName;<br>        File targetFile = <span class="hljs-keyword">new</span> File(<span class="hljs-keyword">this</span>.localStorageRoot, fileName);<br>        <span class="hljs-keyword">try</span> (OutputStream output = <span class="hljs-keyword">new</span> BufferedOutputStream(<span class="hljs-keyword">new</span> FileOutputStream(targetFile))) {<br>            input.transferTo(output);<br>        }<br>        <span class="hljs-keyword">return</span> fileName;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>而云端存储可通过<code>CloudStorageService</code>实现：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Profile("!default")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CloudStorageService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageService</span> </span>{<br>    <span class="hljs-meta">@Value("${storage.cloud.bucket:}")</span><br>    String bucket;<br><br>    <span class="hljs-meta">@Value("${storage.cloud.access-key:}")</span><br>    String accessKey;<br><br>    <span class="hljs-meta">@Value("${storage.cloud.access-secret:}")</span><br>    String accessSecret;<br><br>    <span class="hljs-keyword">final</span> Logger logger = LoggerFactory.getLogger(getClass());<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span></span><br>        logger.info(<span class="hljs-string">"Initializing cloud storage..."</span>);<br>    }<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> InputStream <span class="hljs-title">openInputStream</span><span class="hljs-params">(String uri)</span> <span class="hljs-keyword">throws</span> IOException </span>{<br>        <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span></span><br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IOException(<span class="hljs-string">"File not found: "</span> + uri);<br>    }<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">store</span><span class="hljs-params">(String extName, InputStream input)</span> <span class="hljs-keyword">throws</span> IOException </span>{<br>        <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span></span><br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IOException(<span class="hljs-string">"Unable to access cloud storage."</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到<code>LocalStorageService</code>使用了条件装配<code>@Profile("default")</code>，即默认启用<code>LocalStorageService</code>，而<code>CloudStorageService</code>使用了条件装配<code>@Profile("!default")</code>，即非<code>default</code>环境时，自动启用<code>CloudStorageService</code>。这样，一套代码，就实现了不同环境启用不同的配置。</p>
<h2 id="使用Conditional"><a href="#使用Conditional" class="headerlink" title="使用Conditional"></a>使用Conditional</h2><p>使用Profile能根据不同的Profile进行条件装配，但是Profile控制比较糙，如果想要精细控制，例如，配置本地存储、AWS存储和阿里云存储，将来很可能会增加Azure存储等，用Profile就很难实现。Spring本身提供了条件装配<code>@Conditional</code>，但是自己要写比较复杂的Condition来做判断，比较麻烦。SpringBoot则为我们提供了几个非常实用的条件：</p>
<ul>
<li>@ConditionalOnProperty：如果有指定的配置，条件生效</li>
<li>@ConditionalOnBean：如果有指定的Bean，条件生效</li>
<li>@ConditionalOnMissingBean：如果没有指定的Bean，条件生效</li>
<li>@ConditionalOnMissingClass：如果没有指定的Class，条件生效</li>
<li>@ConditionalOnWebApplication：在Web环境中条件生效</li>
<li>@ConditionalOnExpression：根据表达式判断条件是否生效</li>
</ul>
<p>我们以最常用的<code>@ConditionalOnProperty</code>为例，把上一节的<code>StorageService</code>改写如下。首先，定义配置<code>storage.type=xxx</code>用作判断条件，默认为<code>local</code>：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">storage:</span><br>  <span class="hljs-attr">type:</span> <span class="hljs-string">${STORAGE_TYPE:local}</span><br></code></pre></td></tr></tbody></table></figure>

<p>设定为local时，启动<code>LocalStorageService</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@ConditionalOnProperty(value = "storage.type", havingValue = "local", matchIfMissing = true)</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocalStorageService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageService</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>设定为<code>aws</code>时，启用<code>AwsStorageService</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@ConditionalOnProperty(value = "storage.type", havingValue = "aws")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AwsStorageService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageService</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>设定为<code>aliyun</code>时，启用<code>AliyunStorageService</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@ConditionalOnProperty(value = "storage.type", havingValue = "aliyun")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AliyunStorageService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">StorageService</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到<code>LocalStorageService</code>的注解，当指定配置为<code>local</code>，或者配置不存在，均启用<code>LocalStorageService</code>。由此可见，SpringBoot提供的条件装配使得应用程序更加具有灵活性。</p>
<h2 id="加载配置文件"><a href="#加载配置文件" class="headerlink" title="加载配置文件"></a>加载配置文件</h2><p>加载配置文件可以直接使用注解<code>@Value</code>，例如，我们定义一个最大允许上传的文件大小的配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">storage:</span><br>  <span class="hljs-attr">local:</span><br>    <span class="hljs-attr">max-size:</span> <span class="hljs-number">102400</span><br></code></pre></td></tr></tbody></table></figure>

<p>在某个FileUploader里获取该配置：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileUploader</span> </span>{<br>    <span class="hljs-meta">@Value("${storage.local.max-size:102400}")</span><br>    <span class="hljs-keyword">int</span> maxSize;<br><br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>在另一个UploadFilter中，因为要检查文件的MD5，同时也要检查输入流的大小，因此，也需要该配置：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{<br>    <span class="hljs-meta">@Value("${storage.local.max-size:100000}")</span><br>    <span class="hljs-keyword">int</span> maxSize;<br><br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>多次引用同一个<code>@Value</code>不但麻烦，而且<code>@Value</code>使用字符串，缺少编译器检查，容易造成多处引用不一致。</p>
<p>为了更好地管理配置，SpringBoot允许创建一个Bean，持有一组配置，并由SpringBoot自动注入。</p>
<p>假设我们在<code>application.yml</code>中添加了如下配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">storage:</span><br>  <span class="hljs-attr">local:</span><br>    <span class="hljs-comment"># 文件存储根目录:</span><br>    <span class="hljs-attr">root-dir:</span> <span class="hljs-string">${STORAGE_LOCAL_ROOT:/var/storage}</span><br>    <span class="hljs-comment"># 最大文件大小，默认100K:</span><br>    <span class="hljs-attr">max-size:</span> <span class="hljs-string">${STORAGE_LOCAL_MAX_SIZE:102400}</span><br>    <span class="hljs-comment"># 是否允许空文件:</span><br>    <span class="hljs-attr">allow-empty:</span> <span class="hljs-literal">false</span><br>    <span class="hljs-comment"># 允许的文件类型:</span><br>    <span class="hljs-attr">allow-types:</span> <span class="hljs-string">jpg,</span> <span class="hljs-string">png,</span> <span class="hljs-string">gif</span><br></code></pre></td></tr></tbody></table></figure>

<p>可以首先定义一个JavaBean，持有该组配置：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StorageConfiguration</span> </span>{<br><br>    <span class="hljs-keyword">private</span> String rootDir;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> maxSize;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> allowEmpty;<br>    <span class="hljs-keyword">private</span> List&lt;String&gt; allowTypes;<br><br>    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> getters and setters</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>保证JavaBean的属性名称和配置一致即可，然后，我们添加两个注解：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-meta">@ConfigurationProperties("storage.local")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StorageConfiguration</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意<code>@ConfigurationProperties("storage.local")</code>表示将从配置项<code>storage.local</code>读取该项的所有子项配置，并且，<code>@Configuration</code>表示<code>StorageConfiguration</code>也是一个Spring管理的Bean，可直接注入到其他Bean中：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StorageService</span> </span>{<br>    <span class="hljs-keyword">final</span> Logger logger = LoggerFactory.getLogger(getClass());<br><br>    <span class="hljs-meta">@Autowired</span><br>    StorageConfiguration storageConfig;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        logger.info(<span class="hljs-string">"Load configuration: root-dir = {}"</span>, storageConfig.getRootDir());<br>        logger.info(<span class="hljs-string">"Load configuration: max-size = {}"</span>, storageConfig.getMaxSize());<br>        logger.info(<span class="hljs-string">"Load configuration: allowed-types = {}"</span>, storageConfig.getAllowTypes());<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这样一来，引入<code>storage.local</code>的相关配置就很容易了，因为只需要注入<code>StorageConfiguration</code>这个Bean，这样就可以由编译器检查类型，无需编写重复的<code>@Value</code>注解。</p>
<h2 id="禁用自动配置"><a href="#禁用自动配置" class="headerlink" title="禁用自动配置"></a>禁用自动配置</h2><p>SpringBoot大量使用自动配置和默认配置，极大地减少了代码，通常只需要加上几个注解，并按照默认规则设置一下必要的配置即可。例如，配置JDBC，默认情况下，只需要配置一个<code>spring.datasource</code>：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">datasource:</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:hsqldb:file:testdb</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">sa</span><br>    <span class="hljs-attr">password:</span><br>    <span class="hljs-attr">dirver-class-name:</span> <span class="hljs-string">org.hsqldb.jdbc.JDBCDriver</span><br></code></pre></td></tr></tbody></table></figure>

<p>SpringBoot就会自动创建出<code>DataSource</code>、<code>JdbcTemplate</code>、<code>DataSourceTransactionManager</code>，非常方便。</p>
<p>但是，有时候，我们必须要禁用某些自动配置。例如，系统有主从两个数据库，而SpringBoot的自动配置只能配一个，怎么办？这时，针对<code>DataSource</code>相关的自动配置，就必须关掉。我们需要用<code>exclude</code>指定需要关掉的自动配置：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-comment">// 启动自动配置，但排除指定的自动配置:</span><br><span class="hljs-meta">@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>现在，SpringBoot不再给我们自动创建<code>DataSource</code>、<code>JdbcTemplate</code>和<code>DataSourceTransactionManager</code>了，要实现主从数据库支持，怎么办？</p>
<p>首先，我们需要把主从数据库配置写到<code>application.yml</code>中，仍然按照SpringBoot默认的格式写，但datasource改为<code>datasource-master</code>和<code>datasource-slave</code>：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">datasource-master:</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:hsqldb:file:testdb</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">sa</span><br>    <span class="hljs-attr">password:</span><br>    <span class="hljs-attr">dirver-class-name:</span> <span class="hljs-string">org.hsqldb.jdbc.JDBCDriver</span><br>  <span class="hljs-attr">datasource-slave:</span><br>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:hsqldb:file:testdb</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">sa</span><br>    <span class="hljs-attr">password:</span><br>    <span class="hljs-attr">dirver-class-name:</span> <span class="hljs-string">org.hsqldb.jdbc.JDBCDriver</span><br></code></pre></td></tr></tbody></table></figure>

<p>注意到两个数据库实际上是一个库。如果使用MySQL，可以创建一个只读用户，作为<code>datasource-slave</code>的用户来模拟一个从库。下一步，我们分别创建两个HikariCP的<code>DataSource</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MasterDataSourceConfiguration</span> </span>{<br>    <span class="hljs-meta">@Bean("masterDataSourceProperties")</span><br>    <span class="hljs-meta">@ConfigurationProperties("spring.datasource-master")</span><br>    <span class="hljs-function">DataSourceProperties <span class="hljs-title">dataSourceProperties</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DataSourceProperties();<br>    }<br><br>    <span class="hljs-meta">@Bean("masterDataSource")</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">dataSource</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> <span class="hljs-meta">@Qualifier("masterDataSourceProperties")</span> DataSourceProperties props)</span> </span>{<br>        <span class="hljs-keyword">return</span> props.initializeDataSourceBuilder().build();<br>    }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SlaveDataSourceConfiguration</span> </span>{<br>    <span class="hljs-meta">@Bean("slaveDataSourceProperties")</span><br>    <span class="hljs-meta">@ConfigurationProperties("spring.datasource-slave")</span><br>    <span class="hljs-function">DataSourceProperties <span class="hljs-title">dataSourceProperties</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DataSourceProperties();<br>    }<br><br>    <span class="hljs-meta">@Bean("slaveDataSource")</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">dataSource</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> <span class="hljs-meta">@Qualifier("slaveDataSourceProperties")</span> DataSourceProperties props)</span> </span>{<br>        <span class="hljs-keyword">return</span> props.initializeDataSourceBuilder().build();<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意到上述class并未添加<code>@Configuration</code>和<code>@Component</code>，要使之生效，可是使用<code>@Import</code>导入：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-meta">@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)</span><br><span class="hljs-meta">@Import({ MasterDataSourceConfiguration.class, SlaveDataSourceConfiguration.class})</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>此外，上述两个DataSource的Bean名称分别为<code>masterDataSource</code>和<code>slaveDataSource</code>，我们还需要一个最终的<code>@Primary</code>标注的<code>DataSource</code>，它采用Spring提供的<code>AbstractRoutingDataSource</code>，代码实现如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoutingDataSource</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractRoutingDataSource</span> </span>{<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">protected</span> Object <span class="hljs-title">determineCurrentLookupKey</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-comment">// 从ThreadLocal中取出key:</span><br>        <span class="hljs-keyword">return</span> RoutingDataSourceContext.getDataSourceRoutingKey();<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p><code>RoutingDataSource</code>本身并不是真正的<code>DataSource</code>，它通过Map关联一组<code>DataSource</code>，下面的代码创建了包含两个<code>DataSource</code>的<code>RoutingDataSource</code>，关联的key分别为<code>masterDataSource</code>和<code>slaveDataSource</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoutingDataSourceConfiguration</span> </span>{<br>    <span class="hljs-meta">@Primary</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">DataSource <span class="hljs-title">dataSource</span><span class="hljs-params">(</span></span><br><span class="hljs-params"><span class="hljs-function">            <span class="hljs-meta">@Autowired</span> <span class="hljs-meta">@Qualifier("masterDataSource")</span> DataSource masterDataSource,</span></span><br><span class="hljs-params"><span class="hljs-function">            <span class="hljs-meta">@Autowired</span> <span class="hljs-meta">@Qualifier("slaveDataSource")</span> DataSource slaveDataSource)</span> </span>{<br>        <span class="hljs-keyword">var</span> ds = <span class="hljs-keyword">new</span> RoutingDataSource();<br>        <span class="hljs-comment">// 关联两个DataSource:</span><br>        ds.setTargetDataSources(Map.of(<br>                <span class="hljs-string">"masterDataSource"</span>, masterDataSource,<br>                <span class="hljs-string">"slaveDataSource"</span>, slaveDataSource));<br>        <span class="hljs-comment">// 默认使用masterDataSource:</span><br>        ds.setDefaultTargetDataSource(masterDataSource);<br>        <span class="hljs-keyword">return</span> ds;<br>    }<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">JdbcTemplate <span class="hljs-title">jdbcTemplate</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JdbcTemplate(dataSource);<br>    }<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">DataSourceTransactionManager <span class="hljs-title">dataSourceTransactionManager</span><span class="hljs-params">(<span class="hljs-meta">@Autowired</span> DataSource dataSource)</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DataSourceTransactionManager(dataSource);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>仍然需要自己创建<code>JdbcTemplate</code>和<code>PlatformTransactionManager</code>，注入的是标记为<code>@Primary</code>的<code>RoutingDataSource</code>。</p>
<p>这样，我们通过如下的代码就可以切换<code>RoutingDataSource</code>底层使用的真正的<code>DataSource</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">RoutingDataSourceContext.setDataSourceRoutingKey(<span class="hljs-string">"slaveDataSource"</span>);<br>jdbcTemplate.query(...);<br></code></pre></td></tr></tbody></table></figure>

<p>只不过写代码切换DataSource即麻烦又容易出错，更好的方式是通过注解配合AOP实现自动切换，这样，客户端代码实现如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Controller</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserController</span> </span>{<br>	<span class="hljs-meta">@RoutingWithSlave</span> <span class="hljs-comment">// &lt;-- 指示在此方法中使用slave数据库</span><br>	<span class="hljs-meta">@GetMapping("/profile")</span><br>	<span class="hljs-function"><span class="hljs-keyword">public</span> ModelAndView <span class="hljs-title">profile</span><span class="hljs-params">(HttpSession session)</span> </span>{<br>        ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>实现上述功能需要编写一个<code>@RoutingWithSlave</code>注解，一个AOP织入和一个<code>ThreadLocal</code>来保存key。由于代码比较简单，这里我们不再详述。</p>
<p>如果我们想要确认是否真的切换了<code>DataSource</code>，可以覆写<code>determineTargetDataSource()</code>方法并打印出<code>DataSource</code>的名称：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoutingDataSource</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractRoutingDataSource</span> </span>{<br>    ...<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">protected</span> DataSource <span class="hljs-title">determineTargetDataSource</span><span class="hljs-params">()</span> </span>{<br>        DataSource ds = <span class="hljs-keyword">super</span>.determineTargetDataSource();<br>        logger.info(<span class="hljs-string">"determin target datasource: {}"</span>, ds);<br>        <span class="hljs-keyword">return</span> ds;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>访问不同的URL，可以在日志中看到两个<code>DataSource</code>，分别是<code>HikariPool-1</code>和<code>hikariPool-2</code>：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">2020-06-14 17:55:21.676  INFO 91561 --- [nio-8080-exec-7] c.i.learnjava.config.RoutingDataSource   : determin target datasource: HikariDataSource (HikariPool-1)<br>2020-06-14 17:57:08.992  INFO 91561 --- [io-8080-exec-10] c.i.learnjava.config.RoutingDataSource   : determin target datasource: HikariDataSource (HikariPool-2)<br></code></pre></td></tr></tbody></table></figure>

<p>我们用一个图来表示创建的DataSource以及相关Bean的关系：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs ascii">┌────────────────────┐       ┌──────────────────┐<br>│@Primary            │&lt;──────│   JdbcTemplate   │<br>│RoutingDataSource   │       └──────────────────┘<br>│ ┌────────────────┐ │       ┌──────────────────┐<br>│ │MasterDataSource│ │&lt;──────│DataSource        │<br>│ └────────────────┘ │       │TransactionManager│<br>│ ┌────────────────┐ │       └──────────────────┘<br>│ │SlaveDataSource │ │<br>│ └────────────────┘ │<br>└────────────────────┘<br></code></pre></td></tr></tbody></table></figure>

<p>注意到<code>DataSourceTransactionManager</code>和<code>JdbcTemplate</code>引用的都是<code>RoutingDataSource</code>，所以，这种设计的一个限制就是：在一个请求中，一旦切换了内部数据源，在同一个事务中，不能再切到另一个，否则，<code>DataSourceTransactionManager</code>和<code>JdbcTemplate</code>操作的就不是同一个数据库连接。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>可以通过<code>@EnableAutoConfiguration(exclude = {...})</code>指定禁用的自动配置；</p>
<p>可以通过<code>@Import({...})</code>导入自定义配置。</p>
<h2 id="添加Filter"><a href="#添加Filter" class="headerlink" title="添加Filter"></a>添加Filter</h2><p>我们在Spring中已经学过了集成Filter，本质上就是通过代理，把Spring管理的Bean注册到Servlet容器中，不过步骤比较繁琐，需要配置<code>web.xml</code>。</p>
<p>在SpringBoot中添加一个Filter更简单。SpringBoot会自动扫描所有的<code>FilterRegistrationBean</code>类型的Bean，然后，将他们返回的Filter自动注册到Servlet容器中，无需任何配置。</p>
<p>我们还是以<code>AuthFilter</code>为例，首先编写一个<code>AuthFilterRegistrationBean</code>，它继承自<code>FilterRegistrationBean</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Order(10)</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthFilterRegistrationBean</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FilterRegistrationBean</span>&lt;<span class="hljs-title">Filter</span>&gt; </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    UserService userService;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Filter <span class="hljs-title">getFilter</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AuthFilter();<br>    }<br><br>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{<br>        ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p><code>FilterRegistrationBean</code>本身不是<code>Filter</code>，它实际上是<code>Filter</code>的工厂。Spring Boot会调用<code>getFilter()</code>，把返回的<code>Filter</code>注册到Servlet容器中。因为我们可以在<code>FilterRegistrationBean</code>中注入需要的资源，然后，在返回的<code>AuthFilter</code>中，这个内部类可以引用外部类的所有字段，自然也包括注入的<code>UserService</code>，所以，整个过程完全基于Spring的IoC容器完成。</p>
<p>再注意到<code>AuthFilterRegistrationBean</code>标记了一个<code>@Order(10)</code>，因为Spring Boot支持给多个<code>Filter</code>排序，数字小的在前面，所以，多个<code>Filter</code>的顺序是可以固定的。</p>
<p>我们再编写一个<code>ApiFilter</code>，专门过滤<code>/api/*</code>这样的URL。首先编写一个<code>ApiFilterRegistrationBean</code></p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Order(20)</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApiFilterRegistrationBean</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FilterRegistrationBean</span>&lt;<span class="hljs-title">Filter</span>&gt; </span>{<br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        setFilter(<span class="hljs-keyword">new</span> ApiFilter());<br>        setUrlPatterns(List.of(<span class="hljs-string">"/api/*"</span>));<br>    }<br><br>    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApiFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{<br>        ...<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>这个<code>ApiFilterRegistrationBean</code>和<code>AuthFilterRegistrationBean</code>又有所不同。因为我们要过滤URL，而不是针对所有URL生效，因此，在<code>@PostConstruct</code>方法中，通过<code>setFilter()</code>设置一个<code>Filter</code>实例后，再调用<code>setUrlPatterns()</code>传入要过滤的URL列表。</p>
<h2 id="集成第三方组件"><a href="#集成第三方组件" class="headerlink" title="集成第三方组件"></a>集成第三方组件</h2><p>和Spring相比，使用SpringBoot通过自动配置即成第三方组件通常来说更简单，这一节我们详细介绍如何通过SpringBoot集成常用的第三方组件。</p>
<h3 id="集成Open-API"><a href="#集成Open-API" class="headerlink" title="集成Open API"></a>集成Open API</h3><p>Open API是一个标准，它的主要作用是描述REST API，既可以作为文档给开发者阅读，又可以让机器根据这个文档自动生成客户端代码。在SpringBoot应用中，假设我们编写了一堆REST API，如何添加Open API的支持？</p>
<p>我们只需要在<code>pom.xml</code>中加入以下依赖：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">org.springdoc:springdoc-openapi-ui:1.4.0<br></code></pre></td></tr></tbody></table></figure>

<p>然后直接启动应用，打开浏览器输入<code>http://localhost:8080/swagger-ui.html</code>，立即可以看到自动生成的API文档，这里列出了3个API，来自<code>api-controller</code>（因为定义在<code>ApiController</code>这个类中），点击某个API还可以交互，即输入API参数，点“try it out”，获得运行结果。</p>
<p>因为我们引入<code>springdoc-openapi-ui</code>这个依赖后，它自动引入Swagger UI用来创建API文档。可以给API加入一些描述信息，例如：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping("/api")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApiController</span> </span>{<br>    ...<br>    <span class="hljs-meta">@Operation(summary = "Get specific user object by it's id.")</span><br>	<span class="hljs-meta">@GetMapping("/users/{id}")</span><br>	<span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">user</span><span class="hljs-params">(<span class="hljs-meta">@Parameter(description = "id of the user.")</span> <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">long</span> id)</span> </span>{<br>		<span class="hljs-keyword">return</span> userService.getUserById(id);<br>	}<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p><code>@Operation</code>可以对API进行描述，<code>@Parameter</code>可以对参数进行描述，它们的目的是用于生成API文档的描述信息。</p>
<p>大多数情况下，不需要任何配置，我们就直接得到了一个运行时动态生成的可交互的API文档，该API文档总是和代码保持同步，大大简化了文档的编写工作。要自定义文档的样式、控制某些API显示等，请参考<a target="_blank" rel="noopener" href="https://springdoc.org/">springdoc文档</a>。</p>
<h4 id="配置反向代理"><a href="#配置反向代理" class="headerlink" title="配置反向代理"></a>配置反向代理</h4><p>如果在服务器上，用户访问的域名是<code>https://example.com</code>，但内部是通过类似Nginx这样的反向代理访问实际的Spring Boot应用，比如<code>http://localhost:8080</code>，这个时候，在页面<code>https://example.com/swagger-ui.html</code>上，显示的URL仍然是<code>http://localhost:8080</code>，这样一来，就无法直接在页面执行API，非常不方便。</p>
<p>这是因为Spring Boot内置的Tomcat默认获取的服务器名称是<code>localhost</code>，端口是实际监听端口，而不是对外暴露的域名和<code>80</code>或<code>443</code>端口。要让Tomcat获取到对外暴露的域名等信息，必须在Nginx配置中传入必要的HTTP Header，常用的配置如下：</p>
<figure class="highlight plaintext hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"># Nginx配置<br>server {<br>    ...<br>    location / {<br>        proxy_pass http://localhost:8080;<br>        proxy_set_header Host $host;<br>        proxy_set_header X-Real-IP $remote_addr;<br>        proxy_set_header X-Forwarded-Proto $scheme;<br>        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br>    }<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>然后，在Spring Boot的<code>application.yml</code>中，加入如下配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">server:</span><br>  <span class="hljs-comment"># 实际监听端口:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span><br>  <span class="hljs-comment"># 从反向代理读取相关的HTTP Header:</span><br>  <span class="hljs-attr">forward-headers-strategy:</span> <span class="hljs-string">native</span><br></code></pre></td></tr></tbody></table></figure>

<p>重启Spring Boot应用，即可在Swagger中显示正确的URL。</p>
<h3 id="访问Redis"><a href="#访问Redis" class="headerlink" title="访问Redis"></a>访问Redis</h3><p>在SpringBoot中，要访问Redis，可以直接引入<code>spring-boot-starter-data-redis</code>，它实际上是Spring Data的一个子项目–Spring Data Redis，主要用到了这几个组件：</p>
<ul>
<li>Lettuce：一个基于Netty的高性能Redis客户端</li>
<li>RedisTemplate：一个类似于JdbcTemplate的接口，用于简化Redis的操作</li>
</ul>
<p>因为Spring Data Redis引入的依赖项很多，如果只是为了使用Redis，完全可以只引入Lettuce，剩下的操作由自己来完成。</p>
<p>本节我们稍微深入了解一下Redis的客户端，看怎么一步步把一个第三方组件引入到Spring Boot中。</p>
<p>首先我们添加必要的几个依赖项：</p>
<ul>
<li>io.lettuce:lettuce-core</li>
<li>org.apache.commons:commons-pool2</li>
</ul>
<p>注意我们并未指定版本号，因为在<code>spring-boot-starter-parent</code>中已经把常用组件的版本号确定下来了。第一步是在配置文件<code>application.yml</code>中添加Redis的相关配置：</p>
<figure class="highlight yaml hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">redis:</span><br>    <span class="hljs-attr">host:</span> <span class="hljs-string">${REDIS_HOST:localhost}</span><br>    <span class="hljs-attr">port:</span> <span class="hljs-string">${REDIS_PORT:6379}</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">${REDIS_PASSWORD:}</span><br>    <span class="hljs-attr">ssl:</span> <span class="hljs-string">${REDIS_SSL:false}</span><br>    <span class="hljs-attr">database:</span> <span class="hljs-string">${REDIS_DATABASE:0}</span><br></code></pre></td></tr></tbody></table></figure>

<p>然后，通过<code>RedisConfiguration</code>来加载它：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@ConfigurationProperties("spring.redis")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisConfiguration</span> </span>{<br>	<span class="hljs-keyword">private</span> String host;<br>	<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> port;<br>	<span class="hljs-keyword">private</span> String password;<br>	<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> database;<br><br>    <span class="hljs-comment">// getters and setters...</span><br>}<br></code></pre></td></tr></tbody></table></figure>

<p>再编写一个<code>@Bean</code>方法来创建<code>RedisClient</code>，可以直接放在<code>RedisConfiguration</code>中：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@ConfigurationProperties("spring.redis")</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisConfiguration</span> </span>{<br>    ...<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-function">RedisClient <span class="hljs-title">redisClient</span><span class="hljs-params">()</span> </span>{<br>        RedisURI uri = RedisURI.Builder.redis(<span class="hljs-keyword">this</span>.host, <span class="hljs-keyword">this</span>.port)<br>                .withPassword(<span class="hljs-keyword">this</span>.password)<br>                .withDatabase(<span class="hljs-keyword">this</span>.database)<br>                .build();<br>        <span class="hljs-keyword">return</span> RedisClient.create(uri);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>在启动入口引入该配置：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-meta">@Import(RedisConfiguration.class)</span> <span class="hljs-comment">// 加载Redis配置</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Application</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>注意：如果在<code>RedisConfiguration</code>上标注<code>@Configuration</code>，则可通过SpringBoot的自动扫描机制自动加载，否则，使用<code>@Import</code>手动加载。紧接着，我们用一个RedisService来封装所有Redis操作，基础代码如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisService</span> </span>{<br>    <span class="hljs-meta">@Autowired</span><br>    RedisClient redisClient;<br><br>    GenericObjectPool&lt;StatefulRedisConnection&lt;String, String&gt;&gt; redisConnectionPool;<br><br>    <span class="hljs-meta">@PostConstruct</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{<br>        GenericObjectPoolConfig&lt;StatefulRedisConnection&lt;String, String&gt;&gt; poolConfig = <span class="hljs-keyword">new</span> GenericObjectPoolConfig&lt;&gt;();<br>        poolConfig.setMaxTotal(<span class="hljs-number">20</span>);<br>        poolConfig.setMaxIdle(<span class="hljs-number">5</span>);<br>        poolConfig.setTestOnReturn(<span class="hljs-keyword">true</span>);<br>        poolConfig.setTestWhileIdle(<span class="hljs-keyword">true</span>);<br>        <span class="hljs-keyword">this</span>.redisConnectionPool = ConnectionPoolSupport.createGenericObjectPool(() -&gt; redisClient.connect(), poolConfig);<br>    }<br><br>    <span class="hljs-meta">@PreDestroy</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">shutdown</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">this</span>.redisConnectionPool.close();<br>        <span class="hljs-keyword">this</span>.redisClient.shutdown();<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>上述代码引入Common Pool的一个对象池，用于缓存Redis连接。因为Lettuce本身是基于Netty的异步驱动，在异步访问时并不需要创建连接池，但基于Servlet模型的同步访问时，连接池是必要的。连接池在<code>@PostConstruct</code>方法中初始化，在<code>@PreDestroy</code>方法中关闭。</p>
<p>然后，在RedisService中添加Redis访问方法。为了简化代码，我们仿照<code>JdbcTemplate.execute(ConnectionCallback)</code>方法，传入回调函数，可大幅减少样板代码。首先，定义回调函数接口<code>SyncCommandCallback</code>：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">SyncCommandCallback</span>&lt;<span class="hljs-title">T</span>&gt; </span>{<br>    <span class="hljs-comment">// 在此操作Redis:</span><br>    <span class="hljs-function">T <span class="hljs-title">doInConnection</span><span class="hljs-params">(RedisCommands&lt;String, String&gt; commands)</span></span>;<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>编写<code>executeSync</code>方法，在该方法中，获取Redis连接，利用callback操作Redis，最后释放连接，并返回操作结果：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">executeSync</span><span class="hljs-params">(SyncCommandCallback&lt;T&gt; callback)</span> </span>{<br>    <span class="hljs-keyword">try</span> (StatefulRedisConnection&lt;String, String&gt; connection = redisConnectionPool.borrowObject()) {<br>        connection.setAutoFlushCommands(<span class="hljs-keyword">true</span>);<br>        RedisCommands&lt;String, String&gt; commands = connection.sync();<br>        <span class="hljs-keyword">return</span> callback.doInConnection(commands);<br>    } <span class="hljs-keyword">catch</span> (Exception e) {<br>        logger.warn(<span class="hljs-string">"executeSync redis failed."</span>, e);<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>有的童鞋觉得这样访问Redis的代码太复杂了，实际上我们可以针对常用操作把它封装一下，例如<code>set</code>和<code>get</code>命令：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">set</span><span class="hljs-params">(String key, String value)</span> </span>{<br>    <span class="hljs-keyword">return</span> executeSync(commands -&gt; commands.set(key, value));<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">get</span><span class="hljs-params">(String key)</span> </span>{<br>    <span class="hljs-keyword">return</span> executeSync(commands -&gt; commands.get(key));<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>类似的，<code>hget</code>和<code>hset</code>操作如下：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">hset</span><span class="hljs-params">(String key, String field, String value)</span> </span>{<br>    <span class="hljs-keyword">return</span> executeSync(commands -&gt; commands.hset(key, field, value));<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">hget</span><span class="hljs-params">(String key, String field)</span> </span>{<br>    <span class="hljs-keyword">return</span> executeSync(commands -&gt; commands.hget(key, field));<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">public</span> Map&lt;String, String&gt; <span class="hljs-title">hgetall</span><span class="hljs-params">(String key)</span> </span>{<br>    <span class="hljs-keyword">return</span> executeSync(commands -&gt; commands.hgetall(key));<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>常用命令可以提供方法接口，如果要执行任意复杂的操作，就可以通过<code>executeSync(SyncCommandCallback&lt;T&gt;)</code>来完成。</p>
<p>完成了RedisService后，就可以使用Redis了。例如，，在<code>UserController</code>中，我们在Session中只存放登录用户的ID，用户信息存放到Redis，提供两个方法用于读写：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Controller</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserController</span> </span>{<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String KEY_USER_ID = <span class="hljs-string">"__userid__"</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String KEY_USERS = <span class="hljs-string">"__users__"</span>;<br><br>    <span class="hljs-meta">@Autowired</span> ObjectMapper objectMapper;<br>    <span class="hljs-meta">@Autowired</span> RedisService redisService;<br><br>    <span class="hljs-comment">// 把User写入Redis:</span><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">putUserIntoRedis</span><span class="hljs-params">(User user)</span> <span class="hljs-keyword">throws</span> Exception </span>{<br>        redisService.hset(KEY_USERS, user.getId().toString(), objectMapper.writeValueAsString(user));<br>    }<br><br>    <span class="hljs-comment">// 从Redis读取User:</span><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> User <span class="hljs-title">getUserFromRedis</span><span class="hljs-params">(HttpSession session)</span> <span class="hljs-keyword">throws</span> Exception </span>{<br>        Long id = (Long) session.getAttribute(KEY_USER_ID);<br>        <span class="hljs-keyword">if</span> (id != <span class="hljs-keyword">null</span>) {<br>            String s = redisService.hget(KEY_USERS, id.toString());<br>            <span class="hljs-keyword">if</span> (s != <span class="hljs-keyword">null</span>) {<br>                <span class="hljs-keyword">return</span> objectMapper.readValue(s, User.class);<br>            }<br>        }<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;<br>    }<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>用户登录成功后，把ID放入Session，把<code>User</code>实例放入Redis：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@PostMapping("/signin")</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> ModelAndView <span class="hljs-title">doSignin</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam("email")</span> String email, <span class="hljs-meta">@RequestParam("password")</span> String password, HttpSession session)</span> <span class="hljs-keyword">throws</span> Exception </span>{<br>    <span class="hljs-keyword">try</span> {<br>        User user = userService.signin(email, password);<br>        session.setAttribute(KEY_USER_ID, user.getId());<br>        putUserIntoRedis(user);<br>    } <span class="hljs-keyword">catch</span> (RuntimeException e) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ModelAndView(<span class="hljs-string">"signin.html"</span>, Map.of(<span class="hljs-string">"email"</span>, email, <span class="hljs-string">"error"</span>, <span class="hljs-string">"Signin failed"</span>));<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ModelAndView(<span class="hljs-string">"redirect:/profile"</span>);<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>需要获取<code>User</code>时，从Redis取出：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@GetMapping("/profile")</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> ModelAndView <span class="hljs-title">profile</span><span class="hljs-params">(HttpSession session)</span> <span class="hljs-keyword">throws</span> Exception </span>{<br>    User user = getUserFromRedis(session);<br>    <span class="hljs-keyword">if</span> (user == <span class="hljs-keyword">null</span>) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ModelAndView(<span class="hljs-string">"redirect:/signin"</span>);<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ModelAndView(<span class="hljs-string">"profile.html"</span>, Map.of(<span class="hljs-string">"user"</span>, user));<br>}<br></code></pre></td></tr></tbody></table></figure>

<p>在Redis读写Java对象时，序列化和反序列化是应用程序的工作，上述代码使用JSON作为序列化方案，简单可靠。也可将相关序列化操作封装到RedisService中，这样可以提供更通用的方法：</p>
<figure class="highlight java hljs"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">get</span><span class="hljs-params">(String key, Class&lt;T&gt; clazz)</span> </span>{<br>    ...<br>}<br><br><span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">set</span><span class="hljs-params">(String key, T value)</span> </span>{<br>    ...<br>}<br></code></pre></td></tr></tbody></table></figure>

<p><strong>SpringBoot默认使用Lettuce作为Redis客户端，同步使用时，应通过连接池提高效率。</strong></p>
<h3 id="集成Artemis"><a href="#集成Artemis" class="headerlink" title="集成Artemis"></a>集成Artemis</h3><p>//TODO</p>
</body></html>
    
    </div>
    
    
    <div class="columns is-mobile is-multiline article-nav">
        <span class="column is-12-mobile is-half-desktop  article-nav-prev">
            
            <a href="/Diary/zh-cn/%E5%A6%82%E6%9E%9C%E5%8F%AA%E5%89%A9%E4%B8%89%E4%B8%AA%E5%B0%8F%E6%97%B6%E5%B0%B1%E8%A6%81%E5%87%BA%E7%8E%B0%E4%B8%A7%E5%B0%B8%EF%BC%8C%E4%BD%A0%E4%BC%9A%E5%B9%B2%E4%BB%80%E4%B9%88/">如果只剩三个小时就要出现丧尸，你会干什么</a>
            
        </span>
        <span class="column is-12-mobile is-half-desktop  article-nav-next">
            
            <a href="/Study/Java/Spring/zh-cn/%E9%9B%86%E6%88%90%E7%AC%AC%E4%B8%89%E6%96%B9%E7%BB%84%E4%BB%B6/">集成第三方组件</a>
            
        </span>
    </div>
    
</article>


<div class="sharebox">
    
<div class="sharethis-inline-share-buttons"></div>
<script type='text/javascript' src='//platform-api.sharethis.com/js/sharethis.js#property=608c1408daac690012507aa2&amp;product=sop' async='async'></script>

</div>



    </div>
</section>
    <footer class="footer">
    <div class="container">
        <div class="columns content">
            <div class="column is-narrow has-text-centered">
                &copy; 2021 wanzixin&nbsp;
                Powered by <a href="http://hexo.io/" target="_blank">Hexo</a> & <a
                        target="_blank" rel="noopener" href="http://github.com/ppoffice/hexo-theme-minos">Minos</a>
            </div>
            <div class="column is-hidden-mobile"></div>

            
            <div class="column is-narrow">
                <div class="columns is-mobile is-multiline is-centered">
                
                    
                <a class="column is-narrow has-text-black" title="GitHub" target="_blank" rel="noopener" href="https://github.com/ppoffice/hexo-theme-minos">
                    
                    GitHub
                    
                </a>
                
                </div>
            </div>
            
            
<div class="column is-narrow has-text-centered">
    <div class="dropdown is-up is-right is-hoverable" style="margin-top: -0.2em;">
        <div class="dropdown-trigger">
            <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu7">
                <span class="icon">
                    <i class="fas fa-globe"></i>
                </span>
                <span>简体中文</span>
                <span class="icon is-small">
            <i class="fas fa-angle-down" aria-hidden="true"></i>
          </span>
            </button>
        </div>
        <div class="dropdown-menu has-text-left" role="menu">
            <div class="dropdown-content">
            
                <a href="/Study/Java/SpringBoot/zh-cn/SpringBoot%E5%BC%80%E5%8F%91/" class="dropdown-item">
                    English
                </a>
            
                <a href="/zh-cn/Study/Java/SpringBoot/zh-cn/SpringBoot%E5%BC%80%E5%8F%91/" class="dropdown-item">
                    简体中文
                </a>
            
            </div>
        </div>
    </div>
</div>

        </div>
    </div>
</footer>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script>

<!-- test if the browser is outdated -->
<div id="outdated">
    <h6>Your browser is out-of-date!</h6>
    <p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" target="_blank" rel="noopener" href="http://outdatedbrowser.com/">Update my browser now </a></p>
    <p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.js"></script>
<script>
    $(document).ready(function () {
        // plugin function, place inside DOM ready function
        outdatedBrowser({
            bgColor: '#f25648',
            color: '#ffffff',
            lowerThan: 'flex'
        })
    });
</script>

<script>
    window.FontAwesomeConfig = {
        searchPseudoElements: true
    }
    moment.locale("zh-CN");
</script>


    
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script>
    MathJax.Hub.Config({
        "HTML-CSS": {
            matchFontHeight: false
        },
        SVG: {
            matchFontHeight: false
        },
        CommonHTML: {
            matchFontHeight: false
        },
        tex2jax: {
            inlineMath: [
                ['$','$'],
                ['\\(','\\)']
            ]
        }
    });
</script>

    
    
    
    
<script src="//cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/js/lightgallery-all.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.6.5/js/jquery.justifiedGallery.min.js"></script>
<script>
    (function ($) {
        $(document).ready(function () {
            if (typeof($.fn.lightGallery) === 'function') {
                $('.article.gallery').lightGallery({ selector: '.gallery-item' });
            }
            if (typeof($.fn.justifiedGallery) === 'function') {
                $('.justified-gallery').justifiedGallery();
            }
        });
    })(jQuery);
</script>

    
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script>
    <style>
        .hljs {
            position: relative;
        }

        .hljs .clipboard-btn {
            float: right;
            color: #9a9a9a;
            background: none;
            border: none;
            cursor: pointer;
        }

        .hljs .clipboard-btn:hover {
          color: #8a8a8a;
        }

        .hljs > .clipboard-btn {
            display: none;
            position: absolute;
            right: 4px;
            top: 4px;
        }

        .hljs:hover > .clipboard-btn {
            display: inline;
        }

        .hljs > figcaption > .clipboard-btn {
            margin-right: 4px;
        }
    </style>
    <script>
      $(document).ready(function () {
        $('figure.hljs').each(function(i, figure) {
          var codeId = 'code-' + i;
          var code = figure.querySelector('.code');
          var copyButton = $('<button>Copy <i class="far fa-clipboard"></i></button>');
          code.id = codeId;
          copyButton.addClass('clipboard-btn');
          copyButton.attr('data-clipboard-target-id', codeId);

          var figcaption = figure.querySelector('figcaption');

          if (figcaption) {
            figcaption.append(copyButton[0]);
          } else {
            figure.prepend(copyButton[0]);
          }
        })

        var clipboard = new ClipboardJS('.clipboard-btn', {
          target: function(trigger) {
            return document.getElementById(trigger.getAttribute('data-clipboard-target-id'));
          }
        });
        clipboard.on('success', function(e) {
          e.clearSelection();
        })
      })
    </script>

    
    

    



<script src="/js/script.js"></script>


    
    <div class="searchbox ins-search">
    <div class="searchbox-mask"></div>
    <div class="searchbox-container ins-search-container">
        <div class="searchbox-input-wrapper">
            <input type="text" class="searchbox-input ins-search-input" placeholder="站内搜索" />
            <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="searchbox-result-wrapper ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: '文章',
                PAGES: '页面',
                CATEGORIES: '分类',
                TAGS: '标签',
                UNTITLED: '(无标题)',
            },
            CONTENT_URL: '/content.zh-cn.json',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>

<script src="/js/insight.js"></script>

    
</body>
</html>